/*
 * @Description: axios控制器,实现报错重连及重复请求过滤,不针对FromData类型请求
 * @Author: Cxy
 * @Date: 2024-08-27 10:41:43
 * @LastEditors: Cxy
 * @LastEditTime: 2024-12-22 23:00:07
 * @FilePath: \xy-admin\play\src\api\controller.ts
 */
import type { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from 'axios'

type AxiosControllerError = AxiosError & {
  config: InternalAxiosRequestConfig & { abortKey: symbol; retryCount: number }
}

class Controller {
  #abortControllerOB: Map<symbol, AbortController> = new Map()
  // 初始化重试次数
  #retryCount: number = 3
  // 重试间隔时间
  #intervalTime: number | undefined
  // 模式 默认生产模式下报错重试
  #isErrorReconnect: boolean = true
  #Axios: AxiosInstance
  constructor(
    {
      retryCount,
      intervalTime,
      isErrorReconnect
    }: {
      retryCount?: number
      intervalTime?: number
      isErrorReconnect?: boolean
    },
    Axios: AxiosInstance
  ) {
    retryCount && (this.#retryCount = retryCount)
    intervalTime && (this.#intervalTime = intervalTime)
    this.#Axios = Axios
    typeof isErrorReconnect !== 'undefined' && (this.#isErrorReconnect = isErrorReconnect)

    Axios.interceptors.request.use(
      (config: InternalAxiosRequestConfig & { abortKey?: symbol; retryCount?: number }) => {
        if (config?.abortKey) {
          const controller = new AbortController()
          this.#abortControllerOB.set(config.abortKey, controller)
          config.signal = controller.signal
        }
        if (!config.retryCount) config.retryCount = 0
        return config
      }
    )

    Axios.interceptors.response.use(
      response => {
        response.config &&
          this.abortRequest((response.config as InternalAxiosRequestConfig & { abortKey: symbol }).abortKey)
        return response
      },
      (error: AxiosError & { config: InternalAxiosRequestConfig & { abortKey: symbol; retryCount: number } }) => {
        error.config && this.abortRequest(error.config.abortKey)
        this.#timeoutRetry(error)
        return Promise.reject(error)
      }
    )
  }

  abortRequest(abortKey: symbol) {
    if (!this.#abortControllerOB.has(abortKey)) return
    this.#abortControllerOB.get(abortKey)?.abort()
    this.#abortControllerOB.delete(abortKey)
  }

  async #timeoutRetry(error: AxiosControllerError) {
    if (!error.config || !this.#isErrorReconnect) return Promise.reject(error)
    const config = error.config
    if (config.retryCount === this.#retryCount || !this.#abortControllerOB.has(config.abortKey))
      return Promise.reject(error)
    await new Promise(r => setTimeout(r, (this.#intervalTime || config.retryCount + 1) * 1000))
    config.retryCount++
    return this.#Axios(error.config)
  }
}

export default Controller
